/**
 * @File Name: buffer.cpp
 * @brief  Buffer 类的实现
 * @Author : beyon_sir email:caoboyang.chn@gmail.com
 * @Version : 1.0
 * @Create Date : 2022-11-21
 * 
 */
#include "buffer.h"

Buffer::Buffer(int initBuffSize) : buffer_(initBuffSize), readPos_(0), writePos_(0) {}

size_t Buffer::ReadableBytes() const {
    return writePos_ - readPos_; // 当前已写的数据offset - 当前已读的数据offset
}
size_t Buffer::WritableBytes() const {
    return buffer_.size() - writePos_; // 当前buffer_大小 - 当前已写的数据offset
}

// buffer_中前面已经读取 可重新使用的空间大小
size_t Buffer::PrependableBytes() const {
    return readPos_;
}

const char* Buffer::Peek() const {
    return BeginPtr_() + readPos_;
}

void Buffer::Retrieve(size_t len) {
    assert(len <= ReadableBytes());
    readPos_ += len;
}

void Buffer::RetrieveUntil(const char* end) {
    assert(Peek() <= end );
    Retrieve(end - Peek());
}

void Buffer::RetrieveAll() {
    bzero(&buffer_[0], buffer_.size());
    readPos_ = 0;
    writePos_ = 0;
}

std::string Buffer::RetrieveAllToStr() {
    std::string str(Peek(), ReadableBytes());
    RetrieveAll();
    return str;
}

const char* Buffer::BeginWriteConst() const {
    return BeginPtr_() + writePos_;
}

char* Buffer::BeginWrite() {
    return BeginPtr_() + writePos_;
}

void Buffer::HasWritten(size_t len) {
    writePos_ += len;
} 

void Buffer::Append(const std::string& str) {
    Append(str.data(), str.length());
}

void Buffer::Append(const void* data, size_t len) {
    assert(data);
    Append(static_cast<const char*>(data), len);
}

/// @brief 将溢出数据追加到buffer_中
/// @param str 临时数组
/// @param len 临时数组中数据大小
void Buffer::Append(const char* str, size_t len) {
    assert(str);
    EnsureWriteable(len); // 确保可以写(利用偏移buffer_中数据 or 重新分配空间等方法)
    std::copy(str, str + len, BeginWrite()); // 将溢出数据追加到buffer_中
    HasWritten(len); // 更新writePos_
}

void Buffer::Append(const Buffer& buff) {
    Append(buff.Peek(), buff.ReadableBytes());
}

void Buffer::EnsureWriteable(size_t len) {
    // 如果当前buffer_可写入的空间小于 len 那么重新分配空间
    if(WritableBytes() < len) {
        MakeSpace_(len);
    }
    assert(WritableBytes() >= len);
}

// 具体读取Http Request 并放入buffer_的逻辑
ssize_t Buffer::ReadFd(int fd, int* saveErrno) {
    // 临时的数组 保证能够把所有的数据都读出来
    char buff[65535]; // 64KB

    struct iovec iov[2];
    const size_t writable = WritableBytes();
    /* 分散读， 保证数据全部读完 */
    // buffer_ 信息配置
    iov[0].iov_base = BeginPtr_() + writePos_;
    iov[0].iov_len = writable;
    // buff 临时数组信息配置
    iov[1].iov_base = buff;
    iov[1].iov_len = sizeof(buff);

    // 分散读 readv()
    // 一开始从TCP读缓冲区中读取数据到iov[0],即buffer_中
    // 如果buffer_读满了但是还有数据，那么继续读取数据放入iov[1], 即临时数组buff中
    const ssize_t len = readv(fd, iov, 2);
    if(len < 0) {
        // 记录errno 根据具体错误号决定之后逻辑
        *saveErrno = errno;
    }
    // 如果已写入的数据大小 不大于 当前buffer_可写入的大小
    else if(static_cast<size_t>(len) <= writable) {
        writePos_ += len; // writePos_ 发生偏移
    }
    // 如果已写入的数据大小 大于 当前buffer_可写入的大小 
    // 即溢出 有部分数据在 临时数组buff中
    else {
        writePos_ = buffer_.size();     // writePos_ 偏移到最大值
        Append(buff, len - writable);   // 将溢出数据追加到buffer_中
    }
    return len;
}

ssize_t Buffer::WriteFd(int fd, int* saveErrno) {
    size_t readSize = ReadableBytes();
    ssize_t len = write(fd, Peek(), readSize);
    if(len < 0) {
        *saveErrno = errno;
        return len;
    } 
    readPos_ += len;
    return len;
}

char* Buffer::BeginPtr_() {
    return &*buffer_.begin();
}

const char* Buffer::BeginPtr_() const {
    return &*buffer_.begin();
}

/// @brief 通过扩容/移动满足当前需要的空间
/// @param len 
void Buffer::MakeSpace_(size_t len) {
    // 如果可写入的空间 + 前面可重复利用的空间都小于 当前需要的空间
    if(WritableBytes() + PrependableBytes() < len) {
        // 重新分配空间
        buffer_.resize(writePos_ + len + 1);
    } 
    else {
    // 如果可写入的空间 + 前面可重复利用的空间大于 当前需要的空间
        // 获取当前可读的数据大小
        size_t readable = ReadableBytes(); 
        // 将数据copy 实质上就是把数组中元素向前偏移 为后面添加数据腾挪空间
        std::copy(BeginPtr_() + readPos_, BeginPtr_() + writePos_, BeginPtr_());
        // 可读的位置被置为最开始 0
        readPos_ = 0;
        // 可写的位置被置为 readPos_ + 当前可读的数据大小readable
        writePos_ = readPos_ + readable;
        assert(readable == ReadableBytes());
    }
}